package mbsl.commons.base;

import static mbsl.util.SWTUtils.Direction.down;
import static mbsl.util.SWTUtils.Direction.left;
import static mbsl.util.SWTUtils.Direction.right;
import static mbsl.util.SWTUtils.Direction.up;

import java.util.ArrayList;
import java.util.List;

import mbsl.commons.Commons;
import mbsl.system.TextProvider;
import mbsl.ui.ImageLoader;
import mbsl.ui.dialogs.Dialogs;
import mbsl.util.SWTUtils;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;

public class CommentPanel {
	/** 
	 * Creates the comment panel
	 * @param pParent The parent Composite, onto which the Comment Panel will be placed.
	 * @param pColorTheme The colours the panel should use.  The three first indices (0-2)
	 * describe the colour used on the tab, and the panel background.  The three next (3-5)
	 * describe the colour used on the background of the fields when they are editable. 
	 */
	public CommentPanel(Composite pParent, int[] pColorTheme) {
		mParent = pParent;
		mCommentFolder = new CTabFolder(pParent, SWT.TOP | SWT.MULTI | SWT.FLAT);
		mSaveListeners = new ArrayList<SelectionListener>();
		mDeleteListeners = new ArrayList<SelectionListener>();
		mMoveListeners = new ArrayList<MoveListener>();
		
		mCommentFolder.setBorderVisible(true);
		mCommentFolder.setSelectionBackground(new Color(pParent.getDisplay(), pColorTheme[0], pColorTheme[1], pColorTheme[2]));
		mCommentFolder.setSimple(true);
		mCommentFolder.setTabHeight(-1);

		if (mNormal == null)
			mNormal = pParent.getDisplay().getSystemColor(SWT.COLOR_WHITE);
		if (mEditable == null && pColorTheme.length >= 6)
			mEditable = new Color(pParent.getDisplay(), pColorTheme[3], pColorTheme[4], pColorTheme[5]);
		if (mBoldFont == null)
			mBoldFont = SWTUtils.getBold(pParent.getFont());
		if (mTitleFont == null) {
			FontData font = pParent.getFont().getFontData()[0];
			
			font.setHeight(11);
			font.setStyle(SWT.BOLD);
			
			mTitleFont = new Font(pParent.getDisplay(), font);
		}
		
		addNewTab();
	}
	/**
	 * Sets the LayoutData for this panel.
	 * <p>
	 * A shortcut for getComposite().setLayoutData()
	 */
	public void setLayoutData(Object pLayoutData) {
		mCommentFolder.setLayoutData(pLayoutData);
	}
	/**
	 * Returns this panel as a Composite.
	 */
	public Composite getComposite() {
		return mCommentFolder;
	}
	/** 
	 * Adds a comment to the comment panel of the view dialog.
	 * <p>
	 * Comments are organised in reverse order from that which they were added in, and the most recently added comment will always have focus by default.  It is thus important that care is taken when adding comments, so that they are set in the correct order.
	 * @param pHeading The heading, or title, of the comment.
	 * @param pHeader The information to show in the header field.
	 * @param pBody The body of the comment.
	 */
	public void addComment(String pHeading, String pDate, String pBody) {
		new CommentItem(pHeading, pDate, pBody);
	}
	/**
	 * Removes the comment at the passed index.
	 * @param pIndex The index of the comment to remove.
	 */
	public void removeComment(int pIndex) {
		mCommentFolder.getItem(pIndex).dispose();
	}
	/**
	 * Removes all comments except the New one, and removes the contents of the New comment.
	 */
	public void clear() {
		for (CTabItem item : mCommentFolder.getItems())
			if (item != mNewComment.getTabItem())
				item.dispose();

		mNewComment.setTitle(NEW_TITLE_DEFAULT);
		mNewComment.setBody(null);
	}
	/**
	 * Returns the index of the currently active comment.
	 */
	public int getActiveCommentIndex() {
		return mCommentFolder.getSelectionIndex();
	}
	/**
	 * Returns the currently active comment item.
	 */
	public CommentItem getActiveComment() {
		return (CommentItem)mCommentFolder.getSelection().getData();
	}
	public boolean isNewActive() {
		return mCommentFolder.getSelection() == mNewComment.getTabItem();
	}
	/**
	 * Sets the contents of a comment.
	 * @param pIndex The index of the comment to edit.
	 * @param pHeading The heading to insert into the comment.
	 * @param pBody The body to insert into the comment.
	 */
	public void setComment(int pIndex, String pHeading, String pBody) {
		CommentItem comment = (CommentItem)mCommentFolder.getItem(pIndex).getData();
		
		comment.setBody(pBody);
		comment.setTitle(pHeading);
	}
	/** Adds the New tab to the comment panel */
	private void addNewTab() {
		mNewComment = new CommentItem();
	}
	/**
	 * Adds a listener which will be notified each time the save button is clicked.
	 * @param pListener The listener to add.
	 */
	public void addSaveListener(SelectionListener pListener) {
		mSaveListeners.add(pListener);
	}
	/**
	 * Removes a save event listener from the control.
	 * @param pListener The listener to remove.
	 * @return true if the listener was found, false if not.
	 */
	public boolean removeSaveListener(SelectionListener pListener) {
		return mSaveListeners.remove(pListener);
	}
	/**
	 * Adds a listener which will be notified each time the delete button is clicked.
	 * @param pListener The listener to add.
	 */
	public void addDeleteListener(SelectionListener pListener) {
		mDeleteListeners.add(pListener);
	}
	/**
	 * Removes a delete event listener from the control.
	 * @param pListener The listener to remove.
	 * @return true if the listener was found, false if not.
	 */
	public boolean removeDeleteListener(SelectionListener pListener) {
		return mDeleteListeners.remove(pListener);
	}
	/**
	 * Adds a listener which will be notified each time an item is moved.
	 * @param pListener The listener to add.
	 */
	public void addMoveListener(MoveListener pListener) {
		new mbsl.system.Logger(CommentPanel.class).info("Reordering is not implemented yet in CommentPanel."); // TODO
		
		mMoveListeners.add(pListener);
	}
	/**
	 * Removes a move event listener from the control.
	 * @param pListener The listener to remove.
	 * @return true if the listener was found, false it not.
	 */
	public boolean removeMoveListener(MoveListener pListener) {
		return mMoveListeners.remove(pListener);
	}
	
	/**
	 * An item holding a comment.
	 * <p>
	 * It's basic functionality is providing edit/save and delete/cancel buttons.  It has fields to keep
	 * track of original data when editing, providing native cancel functionality.  This is a slight
	 * departure from the "stupid UI" principle, but hopefully forgivable.  Comments will add themselves
	 * to the TabFolder when created, and CommentItems can be retrieved by doing:
	 * <pre>
	 * CommentItem ci = (CommentItem)commentFolder.getItem(index).getData();
	 * </pre>
	 */
	public final class CommentItem {
		/**
		 * Creates a new empty comment item, and adds it to the panel.
		 * <p>
		 * The item will have the "new" title or icon, have a "save" button, and be permanently editable.
		 */
		private CommentItem() {
			setUpPanel();
			// Set the 'new' icon on the tab
			mItem.setImage(ImageLoader.getImage(mItem.getDisplay(), "new"));
			
			mNew = new Button(mPanel, SWT.PUSH);
			
			mNew.setImage(ImageLoader.getImage(mNew.getDisplay(), "add"));
			mNew.setLayoutData(SWTUtils.getFormData(
					SWTUtils.getMeta(up, 0, BORDER_MARGIN - 1),
					SWTUtils.getMeta(right, 100, -BORDER_MARGIN + 1)
			));
			mNew.addSelectionListener(new SelectionAdapter() {
				public void widgetSelected(SelectionEvent pE) {
					// Route the event to the registered listeners
					for (SelectionListener listener : mSaveListeners)
						listener.widgetSelected(pE);
				}
			});
			mNew.setToolTipText(tp.get("new"));
			
			mTitle.setLayoutData(SWTUtils.getFormData(
					SWTUtils.getMeta(up, 0, BORDER_MARGIN),
					SWTUtils.getMeta(right, mNew, -BORDER_MARGIN + 1),
					SWTUtils.getMeta(left, 0, BORDER_MARGIN)
			));
			
			setInteractive(true);
			
			mTitle.setText(NEW_TITLE_DEFAULT);
			mTitle.setForeground(mParent.getDisplay().getSystemColor(SWT.COLOR_TITLE_INACTIVE_FOREGROUND));
			
			mTitle.addFocusListener(new FocusListener() {
				@Override
				public void focusLost(FocusEvent pE) {
					if (mTitle.getText().trim().isEmpty()) {
						mTitle.setText(NEW_TITLE_DEFAULT);
						mTitle.setForeground(mParent.getDisplay().getSystemColor(SWT.COLOR_TITLE_INACTIVE_FOREGROUND));
					}
				}
				
				@Override
				public void focusGained(FocusEvent pE) {
					if (mTitle.getText().equals(NEW_TITLE_DEFAULT)) {
						mTitle.setText("");
						mTitle.setForeground(mParent.getDisplay().getSystemColor(SWT.COLOR_TITLE_FOREGROUND));
					}
					
				}
			});
			mPanel.layout();
		}
		/**
		 * Creates a new comment item, and adds it to the panel.
		 * <p>
		 * The item will have the title as the tab name, have an "edit" and a "delete" button, and have the "comment" icon.
		 * @param pTitle The title of the comment, which will be used as the tab name.
		 * @param pDate The contents of the Date label.
		 * @param pBody The body of the comment.
		 */
		private CommentItem(String pTitle, String pDate, String pBody) {
			setUpPanel();
			
			mDate = new Label(mPanel, SWT.CENTER);
			
			mDate.setFont(mTitleFont);
			mDate.setForeground(mPanel.getDisplay().getSystemColor(SWT.COLOR_TITLE_INACTIVE_FOREGROUND));
			mDate.setText(pDate);
			mDate.setAlignment(SWT.CENTER);
			
			// Set the 'comment' icon on the tab
			mItem.setImage(ImageLoader.getImage(mItem.getDisplay(), "comment"));
			mItem.setText(pTitle);

			setTitle(pTitle);
			setBody(pBody);
			
			mDelete = new Button(mPanel, SWT.PUSH);

			mDelete.setImage(ImageLoader.getImage(mDelete.getDisplay(), "delete"));
			mDelete.setLayoutData(SWTUtils.getFormData(
					SWTUtils.getMeta(up, 0, BORDER_MARGIN - 1),
					SWTUtils.getMeta(right, 100, -BORDER_MARGIN + 1)
			));
			mDelete.setToolTipText(tp.get("delete"));
			mDelete.addSelectionListener(new SelectionAdapter() {
				public void widgetSelected(SelectionEvent pE) {
					if (mInteractive) { // If we're editing, this is the cancel button, so:
						restoreInput(); // restore what was in the fields earlier
						
						setInteractive(false); // and disable editing.
					} else if (Dialogs.question(mParent.getShell(), String.format(tp.get("delete.confirm"), mTitle.getText()), tp.get("delete.title")) == SWT.YES)
						// If we're not editing, this is the delete button, and if the user agrees, route the event.
						for (SelectionListener listener : mDeleteListeners)
							listener.widgetSelected(pE);
				}
			});
			
			mEdit = new Button(mPanel, SWT.PUSH);
			
			mEdit.setImage(ImageLoader.getImage(mEdit.getDisplay(), "edit"));
			mEdit.setLayoutData(SWTUtils.getFormData(
					SWTUtils.getMeta(up, 0, BORDER_MARGIN - 1),
					SWTUtils.getMeta(right, mDelete, -BORDER_MARGIN + 2)
			));
			mEdit.setToolTipText(tp.get("edit"));
			mEdit.addSelectionListener(new SelectionAdapter() {
				public void widgetSelected(SelectionEvent pE) {
					if (mInteractive) // If we're editing, this is the save button, so just route the event.
						for (SelectionListener listener : mSaveListeners)
							listener.widgetSelected(pE);
					else // If we're not, this is the edit button, so
						saveInput(); // save what was is in the fields.
					
					setInteractive(!mInteractive); // Reverse the editing setting.
				}
			});
			
			mDate.setLayoutData(SWTUtils.getFormData(
					SWTUtils.getMeta(up, 0, BORDER_MARGIN + 3),
					SWTUtils.getMeta(right, mEdit, -BORDER_MARGIN - 10)
			));
			
			mTitle.setLayoutData(SWTUtils.getFormData(
					SWTUtils.getMeta(up, 0, BORDER_MARGIN),
					SWTUtils.getMeta(right, mDate, -BORDER_MARGIN -10),
					SWTUtils.getMeta(left, 0, BORDER_MARGIN)
			));
			mShadowTitle.setLayoutData(SWTUtils.getFormData(
					SWTUtils.getMeta(up, 0, BORDER_MARGIN + 3),
					SWTUtils.getMeta(right, mDate, -BORDER_MARGIN -10),
					SWTUtils.getMeta(left, 0, BORDER_MARGIN + 7)
			));

			setInteractive(false);

			mPanel.layout();
		}
		/** Sets up the basic panel. */
		private void setUpPanel() {
			mItem = new CTabItem(mCommentFolder, SWT.NONE, 0);
			mPanel = new Composite(mCommentFolder, SWT.NONE);
			
			mItem.setControl(mPanel);
			mItem.setFont(mBoldFont);
			mItem.setData(this);
			
			mPanel.setLayout(new FormLayout());
			mPanel.setBackground(mCommentFolder.getSelectionBackground());
			
			mTitle = new Text(mPanel, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
			// The shadow title is shown when not in editing mode
			mShadowTitle = new Label(mPanel, SWT.LEAD);
			
			mTitle.setFont(mTitleFont);
			mShadowTitle.setFont(mTitle.getFont());
			mShadowTitle.setVisible(false);

			// This listener ensures that changes happen to both.
			mTitle.addModifyListener(new ModifyListener() {
				public void modifyText(ModifyEvent pE) {
					if (mTitle.getText().equals(NEW_TITLE_DEFAULT))
						return;
					
					mShadowTitle.setText(mTitle.getText());
					mItem.setText(mTitle.getText());
				}
			});
			
			mBody = new Text(mPanel, SWT.MULTI | SWT.LEAD | SWT.WRAP | SWT.BORDER | SWT.V_SCROLL);
			
			mBody.setLayoutData(SWTUtils.getFormData(
					SWTUtils.getMeta(up, mTitle, BORDER_MARGIN),
					SWTUtils.getMeta(left, 0, BORDER_MARGIN),
					SWTUtils.getMeta(right, 100, -BORDER_MARGIN),
					SWTUtils.getMeta(down, 100, -BORDER_MARGIN)
			));
			
			mCommentFolder.setSelection(0);
		}
		/** Sets whether the fields are interactive, so that the user can edit them. */
		private void setInteractive(boolean pInteractive) {
			mInteractive = pInteractive; // Some of the listeners need to know this.
			
			// Setting backgrounds, interactivity and visibility
			mBody.setBackground(pInteractive ? mEditable : mNormal);
			mBody.setEditable(pInteractive); // Applying the simple switches
			mTitle.setBackground(pInteractive ? mEditable : mNormal);
			mTitle.setEditable(pInteractive);
			mTitle.setVisible(pInteractive);
			mShadowTitle.setVisible(!pInteractive);

			if (mEdit == null && mDelete == null) // This is the case with the special "new comment" comment.
				return; // It doesn't need these lower parts.
			
			String leftImage; // What image the edit (left) button shall use
			String leftToolTip; // The tool tip text of the edit (left) button
			String rightImage; // What image the delete (right) button shall use
			String rightToolTip; // The tool tip text of the delete (right) button
			
			if (pInteractive) { // If we're in editing mode
				leftImage = "save"; // we want a save button
				leftToolTip = tp.get("save");
				rightImage = "exit"; // and a cancel button
				rightToolTip = tp.get("exit");
			} else { // If we're not in editing mode
				leftImage = "edit"; // we want an edit button
				leftToolTip = tp.get("edit");
				rightImage = "delete"; // and a delete button
				rightToolTip = tp.get("delete");
			}

			mEdit.setImage(ImageLoader.getImage(mEdit.getDisplay(), leftImage));
			mEdit.setToolTipText(leftToolTip);
			mDelete.setImage(ImageLoader.getImage(mEdit.getDisplay(), rightImage));
			mDelete.setToolTipText(rightToolTip);
		}
		/** Saves the current field contents to memory. */
		private void saveInput() {
			mTitleBackup = mTitle.getText();
			mBodyBackup = mBody.getText();
		}
		/** Restores the field contents from memory. */
		private void restoreInput() {
			mTitle.setText(mTitleBackup);
			mBody.setText(mBodyBackup);
			
			mTitleBackup = null;
			mBodyBackup = null;
		}
		/** Sets the comment title to <i>pTitle</i> */
		public void setTitle(String pTitle) {
			if (pTitle == null)
				pTitle = "";
			
			mTitle.setText(pTitle);
		}
		/** Returns the title of this comment */
		public String getTitle() {
			return mTitle.getText();
		}
		/** Sets the body text to <i>pBody</i> */
		public void setBody(String pBody) {
			if (pBody == null)
				pBody = "";
			
			mBody.setText(pBody);
		}
		/** Returns the body text of this comment */
		public String getBody() {
			return mBody.getText();
		}
		private CTabItem getTabItem() {
			return mItem;
		}

		private Button mNew;
		private Button mEdit;
		private Button mDelete;
		private CTabItem mItem;
		private Text mTitle;
		private Label mShadowTitle;
		private Label mDate;
		private Text mBody;
		private Composite mPanel;
		private boolean mInteractive;
		
		private String mTitleBackup;
		private String mBodyBackup;
	}
	/**
	 * A listener for move events.
	 */
	public interface MoveListener {
		/**
		 * Called by observed objects to indicate something has moved.
		 * @param index The index the object has moved from.
		 * @param toIndex The index the object has moved to.
		 */
		public void moved(int index, int toIndex);
	}

	private static final int BORDER_MARGIN = 4;
	/** Our own TextProvider with the common.comments prefix */
	private static TextProvider tp = Commons.tp.prefix("common.comments");
	/** The text which will be shown in the title area of the New comment. */
	private static String NEW_TITLE_DEFAULT = tp.get("newTitleDefault");
	/** The background colour used on the fields are in their normal (non-interactive) state.  White probably. */
	private static Color mNormal;
	/** The background colour used on the fields when they can be edited.  Light blue or some such. */
	private Color mEditable;
	/** The bold version of the appropriate system font, directed by the OS. */
	private static Font mBoldFont;
	/** The system font, tweaked to be used in titles. */
	private static Font mTitleFont;
	/** The parent Composite */
	private Composite mParent;
	/** This panel's container, and TabFolder */
	private CTabFolder mCommentFolder;
	/** The New comment */
	private CommentItem mNewComment;
	/** Our listeners listening for Save events */
	private List<SelectionListener> mSaveListeners;
	/** Our listeners listening for Delete events */
	private List<SelectionListener> mDeleteListeners;
	/** Our listeners listening for Move events */
	private List<MoveListener> mMoveListeners;
}
